package db

import (
	"database/sql"
	"errors"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"sync"
	"time"
)

var AutoCloseTimeSecond = 100

var mapMysql map[string]*sql.DB

var connectLock sync.Mutex

type config struct {
	host     string
	port     int
	user     string
	password string
	dbName   string
}

type order struct {
	key   string
	order string
}

type where struct {
	key       string
	value     interface{}
	connector string
	op        string
	isNot     bool
}

type Mysql struct {
	conn      *sql.DB
	Master    *config
	dsn       string
	args      []interface{}
	tableName string
	wheres    []where
	fields    []string
	limit     int
	offset    int
	orderBy   []order
	isClear   bool
}

func singleton(dsn string) bool {
	return mapMysql[dsn] == nil
}

func NewMysqlDB(host string, port int, user string, password string, dbName string) *Mysql {
	this := new(Mysql)
	this.Master = &config{host, port, user, password, dbName}
	return this
}

func SetAutoCloseTime(seconds int) {
	AutoCloseTimeSecond = seconds
}

func (s *Mysql) Select(fields ...string) *Mysql {
	if len(fields) > 0 {
		for _, field := range fields {
			s.fields = append(s.fields, snakeString(field))
		}
	}

	return s
}

func (s *Mysql) Table(table string) *Mysql {
	s.tableName = snakeString(table)

	return s
}

func (s *Mysql) WhereOr(key string, value interface{}) *Mysql {
	s.wheres = append(s.wheres, where{snakeString(key), value, "OR", "=", false})

	return s
}

func (s *Mysql) Where(key string, value interface{}) *Mysql {
	s.wheres = append(s.wheres, where{snakeString(key), value, "AND", "=", false})

	return s
}

func (s *Mysql) WhereNot(key string, value interface{}) *Mysql {
	s.wheres = append(s.wheres, where{snakeString(key), value, "AND", "=", true})

	return s
}

func (s *Mysql) WhereOrNot(key string, value interface{}) *Mysql {
	s.wheres = append(s.wheres, where{snakeString(key), value, "OR", "=", true})

	return s
}

func (s *Mysql) WhereLike(key string, value string) *Mysql {
	s.wheres = append(s.wheres, where{snakeString(key), value, "AND", "LIKE", false})

	return s
}

func (s *Mysql) WhereNotLike(key string, value string) *Mysql {
	s.wheres = append(s.wheres, where{snakeString(key), value, "AND", "LIKE", true})

	return s
}

func (s *Mysql) OrderBy(key string, orderStr string) *Mysql {
	s.orderBy = append(s.orderBy, order{snakeString(key), orderStr})
	return s
}

func (s *Mysql) Skip(offset int) *Mysql {
	s.offset = offset

	return s
}

func (s *Mysql) Take(count int) *Mysql {
	s.limit = count

	return s
}

func (s *Mysql) Limit(count int) *Mysql {
	s.limit = count

	return s
}

func (s *Mysql) Connection() error {

	// 创建connection容器
	if mapMysql == nil {
		mapMysql = make(map[string]*sql.DB)
	}

	// 检查配置
	if s.Master == nil {
		return errors.New("DB Config Not Found")
	}

	dsn := fmt.Sprintf(
		"%s:%s@(%s:%d)/%s?charset=utf8",
		s.Master.user,
		s.Master.password,
		s.Master.host,
		s.Master.port,
		s.Master.dbName,
	)

	// 加锁
	connectLock.Lock()
	defer connectLock.Unlock()

	// 判断单例
	if !singleton(dsn) {
		return nil
	}

	// 创建连接
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return err
	}

	// 设置链接最大生命周期
	if AutoCloseTimeSecond > 0 {
		db.SetConnMaxLifetime(time.Duration(AutoCloseTimeSecond) * time.Second)
	}

	s.conn = db
	s.dsn = dsn
	mapMysql[dsn] = db

	return nil
}

func (s *Mysql) Ping() error {
	return s.conn.Ping()
}

func (s *Mysql) Query(sql string, data ...interface{}) (Rows, error) {
	// 链接数据库
	if err := s.Connection(); err != nil {
		return nil, err
	}

	rows, err := s.conn.Query(sql, data...)
	if err != nil {
		return nil, err
	}

	defer rows.Close()
	return s.rowsToAry(rows)
}

func (*Mysql) rowsToAry(rows *sql.Rows) (Rows, error) {
	fields, err := rows.Columns()
	if err != nil {
		return nil, err
	}

	fieldLen := len(fields)
	scanArgs := make([]interface{}, fieldLen)
	values := make([][]byte, fieldLen)

	for i := range values {
		scanArgs[i] = &values[i]
	}

	resRows := Rows{}
	for rows.Next() {
		if err = rows.Scan(scanArgs...); err != nil {
			return nil, err
		}

		resRow := Row{}
		for i, col := range values {
			resRow[fields[i]] = string(col)
		}
		resRows = append(resRows, resRow)
	}

	return resRows, nil
}

func (s *Mysql) GetOne() (Row, error) {
	s.isClear = false
	s.Take(1)
	sqlStr, err := s.getQuerySql()
	logInfo(fmt.Sprint(sqlStr, s.args))

	rows := Rows{}
	if err == nil {
		rows, err = s.Query(sqlStr, s.args...)
	}
	s.reset()
	if err != nil {
		logError(fmt.Sprint(err))
		return nil, err
	}

	return rows[0], nil
}

func (s *Mysql) GetAll() (Rows, error) {
	s.isClear = false
	result := Rows{}
	sqlStr, err := s.getQuerySql()
	logInfo(fmt.Sprint(sqlStr, s.args))

	if err == nil {
		result, err = s.Query(sqlStr, s.args...)
	}

	s.reset()
	if err != nil {
		logError(fmt.Sprint(err))
		return nil, err
	}
	return result, nil
}

func (s *Mysql) reset() {
	if s.isClear == true {
		return
	}

	s.args = []interface{}{}
	s.tableName = ""
	s.wheres = []where{}
	s.fields = []string{}
	s.limit = 0
	s.offset = 0
	s.orderBy = []order{}
	s.isClear = true
}

func (s *Mysql) Exec(sqlStr string, data ...interface{}) (int, error) {
	if err := s.Connection(); err != nil {
		return 0, err
	}
	res, err := s.conn.Exec(sqlStr, data...)
	if err != nil {
		return 0, err
	}
	var row int64
	row, err = res.LastInsertId()
	if err != nil {
		return 0, err
	}
	if row == 0 {
		row, _ = res.RowsAffected()
	}
	s.reset()
	return int(row), nil
}

func (s *Mysql) Insert(data map[string]interface{}) (int, error) {
	sqlStr := s.getInsertSql(data)
	logInfo(fmt.Sprint(sqlStr, s.args))

	res, err := s.Exec(sqlStr, s.args...)
	if err != nil {
		logError(fmt.Sprint(err))
		return 0, err
	}

	return res, nil
}

func (s *Mysql) BatchInsert(data []map[string]interface{}) (int, error) {
	sqlStr := s.getBatchInsertSql(data)
	logInfo(fmt.Sprint(sqlStr, s.args))

	res, err := s.Exec(sqlStr, s.args...)
	if err != nil {
		logError(fmt.Sprint(err))
		return 0, err
	}

	return res, nil
}

func (s *Mysql) Update(data map[string]interface{}) (int, error) {
	sqlStr := s.getUpdateSql(data)
	logInfo(fmt.Sprint(sqlStr, s.args))

	res, err := s.Exec(sqlStr, s.args...)
	if err != nil {
		logError(fmt.Sprint(err))
		return 0, err
	}

	return res, nil
}

func (s *Mysql) Delete() (int, error) {
	sqlStr := s.getDeleteSql()
	logInfo(fmt.Sprint(sqlStr, s.args))

	res, err := s.Exec(sqlStr, s.args...)
	if err != nil {
		logError(fmt.Sprint(err))
		return 0, err
	}

	return res, nil
}

func (s *Mysql) Close() error {
	err := s.conn.Close()
	if err != nil {
		return err
	}
	mapMysql[s.dsn] = nil
	return nil
}
